package {
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.display.Shape;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.StaticText;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.ui.Mouse;
	import flash.text.AntiAliasType;
	import flash.display.SimpleButton;
	import flash.display.DisplayObject;

	public class Groovity extends Sprite
	{
		public static const G:Number = 0.05;
		public static const MAX_ORB_SIZE:Number = 100;
		public static const MIN_ORB_SIZE:Number = 1;

		public static const NUM_MODES:int = 3;
		public static const MODE_MATTER:Number = 0;
		public static const MODE_ANTIMATTER:Number = 1;
		public static const MODE_OMNIMATTER:Number = 2;
		
		protected var uiLayer:MovieClip;
		protected var gameLayer:MovieClip;
		protected var text:TextField;
		protected var preOrb:MovieClip;
		
		protected var orbs:Array;
		protected var orbSize:Number;
		protected var slinging:Boolean;
		protected var mode:int;
		
		public function Groovity()
		{
			uiLayer = new MovieClip();
			uiLayer.graphics.beginFill(0xCCCCCC, 0.8);
			uiLayer.graphics.drawRect(0, 0, 170, 140);
			uiLayer.graphics.endFill();
			gameLayer = new MovieClip();
			addChild(gameLayer);
			addChild(uiLayer); //put ui over top of game
			
			orbs = new Array();
			preOrb = new MovieClip();
			gameLayer.addChild(preOrb);
			slinging = false;
			orbSize = 5;
			mode = MODE_MATTER;
			
			var sun:Orb = new Orb(1, 80, 0xDDDD00);
			sun.px = stage.stageWidth / 2;
			sun.py = stage.stageHeight / 2;
			orbs.push(sun);
			gameLayer.addChild(sun);
			
			var earth:Orb = new Orb(1, 3, 0x11EE11);
			earth.px = stage.stageWidth / 2 + 200;
			earth.py = stage.stageHeight / 2;
			earth.vy = 2;
			orbs.push(earth);
			gameLayer.addChild(earth);

			var nameText:TextField = new TextField();
			nameText.text = "GROOVITY!";
			nameText.selectable = false;
			nameText.tabEnabled = false;
			nameText.autoSize = "left";
			nameText.x = 10;
			nameText.y = 0;
			nameText.antiAliasType = AntiAliasType.NORMAL;
			var format:TextFormat = new TextFormat();
			format.color = 0xBB1111;
			format.size = 24;
			format.font = "Bodoni MT";
			format.bold = true;
			nameText.setTextFormat(format);
			uiLayer.addChild(nameText);
			
			//Switch matter/antimatter mode button
			var modeText:TextField = new TextField();
			modeText.text = "Switch matter type...";
			modeText.autoSize = "left";
			modeText.x = 0;
			modeText.y = 0;
			var normalButtonImg:MovieClip = new MovieClip;
			normalButtonImg.addChild(modeText);
			normalButtonImg.graphics.beginFill(0xEE4040, 1);
			normalButtonImg.graphics.drawRect(0, 0, uiLayer.width - 10, 20);
			normalButtonImg.graphics.endFill();
			var clickButtonImg:MovieClip = new MovieClip;
			var modeTextCopy:TextField = new TextField(); //*shrug*. Whatever...
			modeTextCopy.text = "Switch matter type...";
			modeTextCopy.autoSize = "left";
			modeTextCopy.x = 0;
			modeTextCopy.y = 0;
			clickButtonImg.addChild(modeTextCopy);
			clickButtonImg.graphics.beginFill(0xEE8080, 1);
			clickButtonImg.graphics.drawRect(0, 0, uiLayer.width - 10, 20);
			clickButtonImg.graphics.endFill();
			var modeButton:SimpleButton = new SimpleButton(normalButtonImg, clickButtonImg, clickButtonImg, clickButtonImg);
			modeButton.x = 5;
			modeButton.y = 30;
			modeButton.addEventListener(MouseEvent.CLICK, onModeClick);
			uiLayer.addChild(modeButton);
			

			text = new TextField();
			text.x = 5;
			text.y = 50;
			text.multiline = false;
			text.autoSize = "left";
			uiLayer.addChild(text);
			
			addEventListener(Event.ENTER_FRAME, update);
			stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
			stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
		}
		
		
		public function update(e:Event = null):void {
			var orb:Orb;
			
			var matterCount:int = 0, antiCount:int = 0;
			var totalMass:Number = 0;
			for each (orb in orbs) {
				totalMass += orb.Mass;
				if (orb.mode == MODE_MATTER)matterCount++;
				else if (orb.mode == MODE_ANTIMATTER) antiCount++;
			}
			
			var info:String = "Mode: ";
			switch (mode) {
				case MODE_MATTER: info += "Matter\n"; break;
				case MODE_ANTIMATTER: info += "AntiMatter\n"; break;
				case MODE_OMNIMATTER: info += "OmniMatter\n"; break;
			}
			info += "Total Bodies: " + orbs.length + "\n";
			info += "Matter Bodies: " + matterCount + "\n";
			info += "Antimatter Bodies: " + antiCount + "\n";
			info += "Universal Mass: " + totalMass.toPrecision(5) + "\n";
			text.text = info;
			
			var steps:int = 10;
			var dt:Number = 1 / steps;
			//Explicit timestepping
			for (var step:int = 0; step < steps; step++) {
				
				//Accumulated and apply groovity forces
				for (var i:int = 0; i < orbs.length; i++) {
					for (var j:int = i + 1; j < orbs.length; j++) {
						//trace("Calculating for " + i + " and " + j);
						var a:Orb = orbs[i];
						var b:Orb = orbs[j];
						var f:Vector2 = Orb.getForceBetween(a, b);
						f.scale(dt);
						if (a.InvMass != 0)
							a.vx += f.x * a.InvMass; a.vy += f.y * a.InvMass;
						if (b.InvMass != 0)
							b.vx -= f.x * b.InvMass; b.vy -= f.y * b.InvMass;
					}
				}
				
				//Positional update from modified velocity
				for each (orb in orbs) {
					orb.px += (orb.vx * dt);
					orb.py += (orb.vy * dt);
				}
			}
			
			
			//Collide and remove smaller orb
			var toRemove:Array = new Array();
			for (i = 0; i < orbs.length; i++) {
				for (j = i + 1; j < orbs.length; j++) {
					a = orbs[i];
					b = orbs[j];
					var delt:Vector2 = new Vector2(b.px - a.px, b.py - a.py);
					var lenSqrd:Number = delt.lenSqrd();
					var minDistSqrd:Number = a.Radius + b.Radius; minDistSqrd *= minDistSqrd;
					if (lenSqrd < minDistSqrd) {
						//trace("Collision! Transferrring mass!");
						var smaller:Orb = (a.Radius < b.Radius) ? a : b;
						var larger:Orb = (a.Radius < b.Radius) ? b : a;
						var overlap:Number = (a.Radius + b.Radius) - Math.sqrt(lenSqrd);
						var massTransfer:Number = smaller.getOuterLayerMass(overlap);
						var smallerGone:Boolean = false;
						//matter hitting matter or anti hitting anti or either type hitting omni
						if (larger.mode == smaller.mode || larger.mode == MODE_OMNIMATTER || smaller.mode == MODE_OMNIMATTER) { 
							larger.changeMass(massTransfer);
							smallerGone = smaller.changeMass( -massTransfer);
						}
						else { //matter hitting antimatter: reduce total of each
							larger.changeMass(-massTransfer);
							smallerGone = smaller.changeMass(-massTransfer);
						}
						if (smallerGone)
							toRemove.push(smaller);
					}
				}
			}
			if (toRemove.length > 0)
				trace("Removal size: " + toRemove.length);
			for each (orb in toRemove) {
				gameLayer.removeChild(orb);
				orbs.splice(orbs.indexOf(orb), 1);
			}
			
			
			//Display
			for each (orb in orbs) {
				orb.x = orb.px;
				orb.y = orb.py;
				orb.draw();
			}
			
			preOrb.graphics.clear();
			if (slinging) {	
				//trace("Drawing slingline at " + preOrb.x + ", " + preOrb.y);
				preOrb.graphics.lineStyle(2, 0xFF0000, 1);
				preOrb.graphics.beginFill(0xFF0000, 1);
				preOrb.graphics.moveTo(0, 0);
				preOrb.graphics.lineTo(preOrb.mouseX, preOrb.mouseY);
				preOrb.graphics.endFill();
			}
			else {
				preOrb.x = mouseX;
				preOrb.y = mouseY;
			}
			drawPreOrb(0,0);
		}
		
		public function onMouseDown(e:MouseEvent):void {
			if (e.target == uiLayer || uiLayer.contains(DisplayObject(e.target))) return; //hack-tastic
			if (slinging) //happens because mouse up and down events don't seem to happen during mouse movement 
				slingAnOrb();
			else {
				slinging = true;
				preOrb.x = mouseX;
				preOrb.y = mouseY;
			}
		}
		
		public function onMouseUp(e:MouseEvent):void {
			trace("Mouseup!");
			if (slinging) {
				slingAnOrb();
			}
		}
		
		public function slingAnOrb():void {
			var orb:Orb = new Orb(1, orbSize, getPreOrbColor());
			orb.px = preOrb.x;
			orb.py = preOrb.y;
			var scale:Number = 0.01;
			orb.vx = (mouseX - preOrb.x) * scale;
			orb.vy = (mouseY - preOrb.y) * scale;
			orb.mode = mode;
			orbs.push(orb);
			gameLayer.addChild(orb);
			trace("Creating new orb at " + orb.x + ", " + orb.y);
			
			slinging = false;
		}
		
		public function onMouseWheel(e:MouseEvent):void {
			var wheel:Number = e.delta;
			orbSize += 3 * wheel;
			if (orbSize < MIN_ORB_SIZE) orbSize = MIN_ORB_SIZE;
			else if (orbSize > MAX_ORB_SIZE) orbSize = MAX_ORB_SIZE;
		}
		
		public function onModeClick(e:MouseEvent):void {
			trace("Switching modes...");
			mode = (mode + 1) % 3;
		}
		
		public function drawPreOrb(x:Number, y:Number):void {
			preOrb.graphics.beginFill(getPreOrbColor(), 1);
			preOrb.graphics.drawCircle(x, y, orbSize);
			preOrb.graphics.endFill();
		}
		
		public function getPreOrbColor():uint {
			var percent:Number = 1 - ((MAX_ORB_SIZE - orbSize) / (MAX_ORB_SIZE - MIN_ORB_SIZE));
			var col:uint = 0x0000FF * (Math.min(percent + 0.5, 1));
			switch (mode) {
				case MODE_MATTER: col = col << 0; break;
				case MODE_ANTIMATTER: col = col << 8; break;
				case MODE_OMNIMATTER: col = col << 16; break;
				default: break;
			}
			return col;
		}
	}
}
